package com.franklin.ideaplugin.easytesting.controllerclient.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.franklin.ideaplugin.easytesting.common.entity.MethodInvokeData;
import com.franklin.ideaplugin.easytesting.common.entity.MethodParameter;
import org.springframework.asm.Type;
import org.springframework.cglib.core.*;
import org.springframework.cglib.proxy.InterfaceMaker;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Ye Junhui
 * @since 2023/6/24 18:02
 */
public abstract class ControllerUtil {

    private final static Type METHOD_INVOKE_DATA_TYPE = Type.getType(MethodInvokeData.class);
    private final static Type STRING_TYPE = Type.getType(String.class);
    private final static Type METHOD_TYPE = Type.getType(Method.class);

    /**
     * 缓存索引
     */
    private final static Map<Class<?>,Map<Method,Integer>> METHOD_INDEX_CACHE = new ConcurrentHashMap<>();

    /**
     * 客户端名称
     *
     * @param clazz
     * @return
     */
    public static String getClientClazzName(Class<?> clazz) {
        String classQualifiedName = clazz.getCanonicalName();
        return classQualifiedName + "$EasyTestingControllerClient";
    }

    /**
     * 客户端名称
     *
     * @param clazz
     * @return
     */
    public static String getClientName(Class<?> clazz) {
        String simpleName = clazz.getSimpleName();
        return StrUtil.lowerFirst(simpleName) + "$EasyTestingControllerClient";
    }

    /**
     * 是否是controller bean
     *
     * @param bean
     * @return
     */
    public static boolean isControllerBean(Object bean) {
        Class<?> clazz = bean.getClass();
        return isControllerClass(clazz);
    }

    /**
     * 是否是controller bean的类
     *
     * @param clazz
     * @return
     */
    public static boolean isControllerClass(Class<?> clazz) {
        Controller controller = AnnotationUtils.findAnnotation(clazz, Controller.class);
        RestController restController = AnnotationUtils.findAnnotation(clazz, RestController.class);
        return Objects.nonNull(controller) || Objects.nonNull(restController);
    }

    /**
     * 构建controller接口
     *
     * @param clazz
     * @return
     */
    public static Class<?> buildControllerInterface(Class<?> clazz) {
        String clientId = getClientClazzName(clazz);
        InterfaceMaker interfaceMaker = new InterfaceMaker();

        //缓存方法名，相同签名方法重载失败
        Map<String,Integer> nameIndexMap = new HashMap<>();
        Method[] declaredMethods = clazz.getDeclaredMethods();
        Map<Method, Integer> methodIntegerMap = METHOD_INDEX_CACHE.get(clazz);
        if (CollectionUtil.isEmpty(methodIntegerMap)){
            methodIntegerMap = new ConcurrentHashMap<>();
            METHOD_INDEX_CACHE.put(clazz,methodIntegerMap);
        }
        Map<Method, Integer> finalMethodIntegerMap = methodIntegerMap;
        Arrays.stream(declaredMethods)
                .filter(method -> Objects.nonNull(AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class)))
                .forEach(method -> {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    Type[] types = new Type[parameterTypes.length + 3];
                    types[0] = METHOD_INVOKE_DATA_TYPE;
                    types[1] = STRING_TYPE;
                    types[2] = METHOD_TYPE;
                    Arrays.fill(types, 3, types.length, STRING_TYPE);

                    //方法名
                    String methodName = method.getName();
                    //默认给-1
                    Integer nameIndex = nameIndexMap.getOrDefault(methodName, -1);
                    //进一位
                    nameIndex = nameIndex + 1;
                    //重置方法名
                    String clientMethodName = getClientMethodName(methodName, nameIndex);
                    //缓存索引
                    nameIndexMap.put(methodName,nameIndex);
                    finalMethodIntegerMap.put(method,nameIndex);

                    //返回值使用字符串
                    Signature signature = new Signature(clientMethodName, Type.getType(String.class), types);
                    interfaceMaker.add(signature, ReflectUtils.getExceptionTypes(method));
                });

        interfaceMaker.setNamingPolicy(new NamingPolicy() {
            @Override
            public String getClassName(String s, String s1, Object o, Predicate predicate) {
                return clientId;
            }
        });

        return interfaceMaker.create();
    }

    /**
     * 获取当前方法的索引
     * @param method
     * @return
     */
    public static Integer getMethodIndex(Method method){
        return METHOD_INDEX_CACHE.get(method.getDeclaringClass()).get(method);
    }

    public static String getClientMethodName(Method method){
        return getClientMethodName(method.getName(),getMethodIndex(method));
    }

    /**
     * 获取客户端方法名
     * @param methodName
     * @param methodIndex
     * @return
     */
    public static String getClientMethodName(String methodName,Integer methodIndex){
        return "easyTesting_" + methodName + "_" + methodIndex;
    }

    /**
     * 方法参数类型
     *
     * @param method
     * @return
     */
    public static Class<?>[] getControllerMethodParameterTypes(Method method) {
        Class<?>[] parameterTypes = method.getParameterTypes();
        Class<?>[] realTypes = new Class<?>[parameterTypes.length + 3];
        realTypes[0] = MethodInvokeData.class;
        realTypes[1] = String.class;
        realTypes[2] = Method.class;
        Arrays.fill(realTypes, 3, realTypes.length, String.class);
        return realTypes;
    }

    /**
     * 获取代理方法的参数
     *
     * @param methodInvokeData
     * @param targetUrl
     * @param originMethod
     * @return
     */
    public static Object[] getProxyMethodParameters(MethodInvokeData methodInvokeData, String targetUrl, Method originMethod) {
        LinkedHashMap<String, MethodParameter> parameterMap = methodInvokeData.getParameterMap();
        Object[] stringArgs = parameterMap.values().stream()
                .map(MethodParameter::getValue)
                .toArray();
        Object[] proxyArgs = new Object[stringArgs.length + 3];
        proxyArgs[0] = methodInvokeData;
        proxyArgs[1] = targetUrl;
        proxyArgs[2] = originMethod;
        for (int i = 0; i < stringArgs.length; i++) {
            proxyArgs[i + 3] = stringArgs[i];
        }
        return proxyArgs;
    }

    /**
     * 是否是请求体
     *
     * @param className
     * @return
     */
    public static boolean isHttpRequestClass(String className) {
        if ("javax.servlet.http.HttpServletRequest".equals(className)) {
            return true;
        }
        if ("org.springframework.http.server.reactive.ServerHttpRequest".equals(className)) {
            return true;
        }
        return false;
    }

    /**
     * 是否是响应体
     *
     * @param className
     * @return
     */
    public static boolean isHttpResponseClass(String className) {
        if ("javax.servlet.http.HttpServletResponse".equals(className)) {
            return true;
        }
        if ("org.springframework.http.server.reactive.ServerHttpResponse".equals(className)) {
            return true;
        }
        return false;
    }

    /**
     * 是否是文件类型
     *
     * @param clazz
     * @return
     */
    public static boolean isFileType(Class<?> clazz) {
        if (clazz.isAssignableFrom(MultipartFile.class)) {
            return true;
        }
        if (clazz.isAssignableFrom(FilePart.class)) {
            return true;
        }
        return false;
    }
}
